#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <stdint.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>

#include <video_manager.h>
#include <convert_manager.h>
#include <disp_manager.h>     
#include <config.h>
#include <render.h>
#include <pic_operation.h>

#include <pthread.h>
#include <sys/ioctl.h>
#include <semaphore.h>
#include <time.h>

#include <track.h>

#include <linux/pxp_dma.h>
#include <linux/pxp_device.h>
#include <sys/mman.h>

#define _GNU_SOURCE

#define PTHREAD_FLAG 1    // 当时写这个主要为了测试虚拟摄像头驱动，但由于后面程序修改过多，暂未确认置0后是否还能正常运行
#define STEP_COEFF 10     // 摄像头相关参数修改步长，这里为亮度

#define MIN(a,b) ((a) < (b) ? (a) : (b))
#define MAX(a,b) ((a) > (b) ? (a) : (b))

#define PAGE_SIZE 4096
#define PAGE_MASK (~(PAGE_SIZE - 1))

#if PTHREAD_FLAG
static sem_t sem0;
static pthread_t tid1,tid2;
#endif
T_VideoBuf tVideoBuf;
PT_VideoBuf tCurrBuf = NULL;
int pic_size;

static int curr_resolution[2];
static int pre_resolution[2];
static int step[2];

static Control_info bright;

// 迭代计算最大公约数
int gcd(int a, int b) {
	while (b != 0) {
		int temp = b;
		b = a % b;
		a = temp;
	}
	return a;
}

// 根据输入执行相应的操作
void operation_func(int fd, OPR_PARM opr)
{
    int pic_fd;
    char filename[100];
    time_t raw_time;
    struct tm *time_info;
    int ret;

    struct v4l2_control control;

    switch(opr)
    {
        case BRIGHTNESS_UP:
        {
            control.id = V4L2_CID_BRIGHTNESS;
            control.value = MIN(bright.curr_val + bright.step * STEP_COEFF, bright.max_val);   //防止超过有效区间
            if(ioctl(fd, VIDIOC_S_CTRL, &control) != 0)
                printf("set failed!\n");
            else
                bright.curr_val = control.value;

            break;
        }
        case BRIGHTNESS_DOWN:
        {
            control.id = V4L2_CID_BRIGHTNESS;
            control.value = MAX(bright.curr_val - bright.step * STEP_COEFF, bright.min_val);
            if(ioctl(fd, VIDIOC_S_CTRL, &control) != 0)
                printf("set failed!\n");
            else
                bright.curr_val = control.value;    
            
            break;
        }
        case RESOLUTION_UP:
        {
            sem_wait(&sem0);
            curr_resolution[0] += step[0];
            curr_resolution[1] += step[1];
            sem_post(&sem0);
            break;
        }
        case RESOLUTION_DOWN:
        {
            sem_wait(&sem0);
            curr_resolution[0] = MAX(curr_resolution[0] - step[0], step[0]);
            curr_resolution[1] = MAX(curr_resolution[1] - step[1], step[1]);


            sem_post(&sem0);
            break;
        }
        case PIC_SAVE:
        {
            if(!tVideoBuf.tPixelDatas.aucPixelDatas)
                return;
            sem_wait(&sem0);   //等待一帧处理完毕再保存
            
            time(&raw_time);   // 获取当前时间
            time_info = localtime(&raw_time);   // 转换为本地时间

            sprintf(filename,"picfile/%02d_%02d_%02d_%02d_%02d.jpg",time_info->tm_mon + 1,time_info->tm_mday,
                time_info->tm_hour,time_info->tm_min,time_info->tm_sec);
            
            pic_fd = open(filename,O_RDWR | O_CREAT,0666);
            if(pic_fd < 0)
            {
                printf("pic file create failed!\n");
                return;
            }
            ret = write(pic_fd,tVideoBuf.tPixelDatas.aucPixelDatas,pic_size);
            if(ret > 0)
                printf("pic %s %d saved!\n",filename,ret);
            else
                printf("write pic failed!\n");
            close(pic_fd);

            sem_post(&sem0);
            
            break;
        }
        case EXIT_PTHREAD:
        {
            printf("exit!\n");
            pthread_cancel(tid1);
            pthread_cancel(tid2);
            break;
        }
        default:
            break;
    }
}

#if PTHREAD_FLAG
void *user_input(void *arg)
{
    char buffer[100];
    int len;
    int fd = (int)(long)arg;

    printf("input pthread start!\n");

    while(1)
    {
        if(fgets(buffer, 100, stdin))
        {
            len = strlen(buffer);
            buffer[len - 1] = '\0';
            //printf("%s",buffer);
            if(strcmp(buffer, "up") == 0)
            {
                operation_func(fd, BRIGHTNESS_UP);
            }
            else if(strcmp(buffer, "down") == 0)
            {
                operation_func(fd, BRIGHTNESS_DOWN);
            }
            else if(strcmp(buffer,"save") == 0)
            {
                operation_func(fd, PIC_SAVE);
            }
            else if(strcmp(buffer,"exit") == 0)
            {
                operation_func(fd, EXIT_PTHREAD);
            }
            else if(strcmp(buffer,"out") == 0)    //放大视频分辨率
            {
                operation_func(fd, RESOLUTION_UP);
            }
            else if(strcmp(buffer,"in") == 0)    //缩小视频分辨率
            {
                operation_func(fd, RESOLUTION_DOWN);
            }
            else
            {
                printf("Invalid command!\n");
            }
        }
    }
}

void *screen_input(void *arg)
{
    int fd = (int)(long)arg;
    GestureType gt;

    printf("screen pthread start!\n");

    while(1)
    {
        gt = get_gesture_type();
        //if(gt)   //仅打印有效手势
        //    printf("gesture type: %d\n", gt);
        if(gt != GESTURE_ERROR)
        {
            switch(gt)
            {
                case GESTURE_DOUBLE_TAP:
                    operation_func(fd, PIC_SAVE);
                    break;
                case GESTURE_SWIPE_UP:
                    operation_func(fd, BRIGHTNESS_UP);
                    break;
                case GESTURE_SWIPE_DOWN:
                    operation_func(fd, BRIGHTNESS_DOWN);
                    break;
                case GESTURE_PINCH_IN:
                    operation_func(fd, RESOLUTION_DOWN);
                    break;
                case GESTURE_PINCH_OUT:
                    operation_func(fd, RESOLUTION_UP);
                    break;
                case GESTURE_SWIPE_LEFT:
                    operation_func(fd, EXIT_PTHREAD);
                    break;
                default:
                    break;
            }
        }
    }
}
#endif

long long GetUs()   //获得当前us级别的系统时间
{
    struct timeval tv;
    gettimeofday(&tv, NULL);
    return tv.tv_sec * 1000000LL + tv.tv_usec;
}


int main(int argc, char **argv)
{
    T_VideoDevice tVideoDevice;
    PT_VideoConvert ptVideoConvert;
    
    T_VideoBuf tConvertBuf;
    //T_VideoBuf tZoomBuf;
    T_VideoBuf tFrameBuf;

    struct v4l2_queryctrl queryctrl;
    struct v4l2_control control;
    
    int err;

    int iPixelFormatOfVideo;
    int iPixelFormatOfDisp;

    int lcdWidth, lcdHeight, lcdBpp;
    int iTopLeftX, iTopLeftY;

    float k;
    int gcd_res;

    bool flag = false;

    T_PixelDatas framebuf;

    int frame_num = 0;

    struct pxp_mem_desc pxp_buffer = {0};
    //struct pxp_mem_desc lcd_buffer = {0};
    struct pxp_config_data config = {0};
    struct pxp_chan_handle pxp_handle = {0};
    int pxp_fd = -1;
    dma_addr_t lcd_phy_addr = 0;


    if(argc != 2)
    {
        printf("Usage: %s </dev/video0,1,...>\n", argv[0]);
        return -1;
    }

    pxp_fd = open("/dev/pxp_device", O_RDWR);
    if(pxp_fd < 0)
    {
        printf("Failed to open pxp_device\n");
        return -1;
    }

    if (ioctl(pxp_fd, PXP_IOC_GET_CHAN, &pxp_handle.handle) < 0) {
        perror("Failed to get PXP channel");
        close(pxp_fd);
        return -1;
    }
    

    /* 各模块的初始化 */
    //注册显示设备
    DisplayInit();
    lcd_phy_addr = SelectAndInitDefaultDispDev("fb");
    GetDispResolution(&lcdWidth, &lcdHeight, &lcdBpp);
    GetVideoBufForDisplay(&tFrameBuf);   //获取显示设备的像素格式，这里是根据Bpp来判断的


    memcpy(&framebuf, &tFrameBuf.tPixelDatas, sizeof(T_PixelDatas));
    framebuf.aucPixelDatas = (unsigned char *)malloc(tFrameBuf.tPixelDatas.iTotalBytes);   //视频缓冲区
    if(framebuf.aucPixelDatas == NULL)
    {
        printf("malloc framebuf failed!\n");
        return -1;
    }

    iPixelFormatOfDisp = tFrameBuf.iPixelFormat;  

    VideoInit();
    err = VideoDeviceInit(argv[1], &tVideoDevice);
    if(err < 0)
    {
        printf("VideoDeviceInit for %s failed!\n",argv[1]);
        return -1;
    }

    /* 获得当前显示屏亮度信息 */
    memset(&queryctrl, 0, sizeof(struct v4l2_queryctrl));
    queryctrl.id = V4L2_CID_BRIGHTNESS;
    if(ioctl(tVideoDevice.iFd, VIDIOC_QUERYCTRL, &queryctrl) != 0)
    {
        printf("query control failed!\n");
        return -1;
    }
    bright.max_val = queryctrl.maximum;
    bright.min_val = queryctrl.minimum;
    bright.step = queryctrl.step;
    printf("brightness : max is %d, min is %d, step is %d\n",queryctrl.maximum,queryctrl.minimum,queryctrl.step);

    control.id = V4L2_CID_BRIGHTNESS;
    if(ioctl(tVideoDevice.iFd,VIDIOC_G_CTRL,&control) != 0)   //获得当前亮度
    {
        printf("brightness get failed!\n");
        return -1;
    }
    bright.curr_val = control.value;

    //pxp_buffer.handle = pxp_handle.handle;
    pxp_buffer.size = tVideoDevice.iWidth * tVideoDevice.iHeight * 4; // 假设每个像素4字节
    pxp_buffer.mtype = MEMORY_TYPE_UNCACHED; // 使用缓存类型

    if (ioctl(pxp_fd, PXP_IOC_GET_PHYMEM, &pxp_buffer) < 0)
    {
        perror("Failed to get physical memory");
        close(pxp_fd);
        return -1;
    }
    pxp_buffer.virt_uaddr = mmap(NULL, pxp_buffer.size, PROT_READ | PROT_WRITE, MAP_SHARED, pxp_fd, pxp_buffer.phys_addr);
    if (pxp_buffer.virt_uaddr == MAP_FAILED) {
        perror("mmap failed");
        return -1;
    }

    // 根据当前分辨率获得之后要缩放的步长
    curr_resolution[0] = tVideoDevice.iWidth;
    curr_resolution[1] = tVideoDevice.iHeight;
    // 根据当前屏幕实际的分辨率修正视频的分辨率
    if(curr_resolution[0] > lcdWidth || curr_resolution[1] > lcdHeight)
    {
        k = (float)curr_resolution[1] / curr_resolution[0];
        curr_resolution[0] = lcdWidth;      //缩放后的宽度为LCD的宽度
        curr_resolution[1] = k * lcdHeight; //等比例缩放
        //上面使用了lcd的width作为基准，这样可能会导致缩放后的高度仍然会超出lcd的高度
        //故如果超过了，则使用lcd的height作为基准
        if(curr_resolution[1] > lcdHeight)  
        {
            curr_resolution[1] = lcdHeight;
            curr_resolution[0] = lcdHeight / k;
        }
    }
    pre_resolution[0] = curr_resolution[0];
    pre_resolution[1] = curr_resolution[1];
    // 计算之后缩放的步长
    gcd_res = gcd(curr_resolution[0], curr_resolution[1]);
    step[0] = curr_resolution[0] / gcd_res;
    step[1] = curr_resolution[1] / gcd_res;

    iPixelFormatOfVideo = tVideoDevice.iPixelFormat;
    printf("Pixel Format of Video: %d\n", iPixelFormatOfVideo);
    printf("Pixel Format of Disp: %d\n", iPixelFormatOfDisp);

    /* 视频格式转化模块初始化 */
    VideoConvertInit();
    ptVideoConvert = GetVideoConvertForFormats(iPixelFormatOfVideo, iPixelFormatOfDisp);
    if(ptVideoConvert == NULL)
    {
        printf("Can not support the format of this device\n");
        return -1;
    }
    ptVideoConvert->Init();   //初始化转化模块  

    /* 初始化触屏模块 */
    err = init_touch_screen();
    if(err < 0)
    {
        printf("init touch screen failed!\n");
        return -1;
    }

    /* 启动设备 */
    err = tVideoDevice.opr->StartDevice(&tVideoDevice);
    if(err < 0)
    {
        printf("StartDevice for %s failed!\n",argv[1]);
        return -1;
    }

    /* 启动用户输入线程 */
    #if PTHREAD_FLAG
    sem_init(&sem0,0,1);
    pthread_create(&tid1, NULL, user_input, (void *)(long)tVideoDevice.iFd);
    pthread_create(&tid2, NULL, screen_input, (void *)(long)tVideoDevice.iFd);
    #endif

    memset(&tVideoBuf, 0, sizeof(T_VideoBuf));
    memset(&tConvertBuf, 0, sizeof(T_VideoBuf));
    tConvertBuf.tPixelDatas.iBpp = lcdBpp;   //在Mjpeg转化Rgb的程序中，需要提前指定这个参数
    tConvertBuf.iPixelFormat = iPixelFormatOfDisp;
    tConvertBuf.tPixelDatas.aucPixelDatas = (unsigned char *)pxp_buffer.virt_uaddr;   //将pxp的物理地址作为转换后的数据缓冲区

    while(1)
    {
        #if PTHREAD_FLAG
        sem_wait(&sem0);
        #endif
        //long long start = GetUs();
        /* 从摄像头获取一帧数据 */
        err = tVideoDevice.opr->GetFrame(&tVideoDevice, &tVideoBuf);
        if(err < 0)
        {
            printf("GetFrame for %s failed!\n",argv[1]);
            return -1;
        }
        tCurrBuf = &tVideoBuf;
        pic_size = tVideoBuf.tPixelDatas.iTotalBytes;

        if (iPixelFormatOfVideo != iPixelFormatOfDisp)
        {
            /* 转换为RGB格式 */
            //long long start2 = GetUs();
            err = ptVideoConvert->Convert(&tVideoBuf, &tConvertBuf);
            //long long end2 = GetUs();
            //printf("TimeCon: %lld us\n", end2 - start2);   //输出上述Zoom函数的执行时间，单位为us
            if(err < 0)
            {
                printf("Video Pixel Format: %s\n", ptVideoConvert->name);
                printf("Convert failed!\n");
                return -1;
            }
            tCurrBuf = &tConvertBuf;
            //pic_size = tConvertBuf.tPixelDatas.iTotalBytes;
        }

        flag = false;

        /* 如果图像分辨率大于LCD，缩放 */
        if(curr_resolution[0] != pre_resolution[0] || curr_resolution[1] != pre_resolution[1])
        {
            flag = true;    //清屏标志位，不清屏的会出现残影
            pre_resolution[0] = curr_resolution[0];
            pre_resolution[1] = curr_resolution[1];
        }

        iTopLeftX = (lcdWidth - curr_resolution[0]) / 2;
        iTopLeftY = (lcdHeight - curr_resolution[1]) / 2;


        #if 1
        config.s0_param.paddr = pxp_buffer.phys_addr;
        config.s0_param.width = tCurrBuf->tPixelDatas.iWidth;
        config.s0_param.height = tCurrBuf->tPixelDatas.iHeight;
        config.s0_param.pixel_fmt = PXP_PIX_FMT_ARGB32;  // 源图像格式
        config.s0_param.stride = tCurrBuf->tPixelDatas.iLineBytes;
        //config.s0_param.local_alpha_enable = true;
        //config.s0_param.color_key_enable = true;

        config.proc_data.srect.top = 0;
        config.proc_data.srect.left = 0;
        config.proc_data.srect.width = tCurrBuf->tPixelDatas.iWidth;
        config.proc_data.srect.height = tCurrBuf->tPixelDatas.iHeight;

        config.out_param.paddr = lcd_phy_addr;
        config.out_param.width = lcdWidth;
        config.out_param.height = lcdHeight;
        config.out_param.pixel_fmt = PXP_PIX_FMT_ARGB32;
        //config.out_param.local_alpha_enable = true;
        //config.out_param.color_key_enable = true;

        config.proc_data.drect.left = iTopLeftX;
        config.proc_data.drect.top = iTopLeftY;
        config.proc_data.drect.width = curr_resolution[0];
        config.proc_data.drect.height = curr_resolution[1];

        config.proc_data.scaling = 1;
        config.layer_nr = 2; 


        if(ioctl(pxp_fd, PXP_IOC_CONFIG_CHAN, &config) < 0) {
            printf("PXP configuration failed\n");
            close(pxp_fd);
            return -1;
        }
        
        if(ioctl(pxp_fd, PXP_IOC_START_CHAN, &pxp_handle.handle) < 0) {
            printf("PXP start failed\n");
            close(pxp_fd);
            return -1;
        }

        if(ioctl(pxp_fd, PXP_IOC_WAIT4CMPLT,&pxp_handle) < 0) {
            printf("PXP wait failed\n");
            close(pxp_fd);
            return -1;
        }

        #endif

        #if 0    // 这里是我在使用PXP之间的代码，缩放是通过函数实现的，效率比较低
        // 如果当前视频分辨率不等于摄像头的分辨率，则缩放
        //printf("%d %d %d %d\n",curr_resolution[0],curr_resolution[1],tCurrBuf->tPixelDatas.iWidth,tCurrBuf->tPixelDatas.iHeight);
        if(curr_resolution[0] != tCurrBuf->tPixelDatas.iWidth || curr_resolution[1] != tCurrBuf->tPixelDatas.iHeight)
        {
            //long long start0 = GetUs();
            /* 主要就是下面这个图像缩放函数，执行时间过长！ */
            MyPicZoom(&tCurrBuf->tPixelDatas, &tFrameBuf.tPixelDatas, curr_resolution[0], curr_resolution[1],flag);
            //long long end0 = GetUs();
            //printf("TimeZoom: %lld us\n", end0 - start0);   //输出上述Zoom函数的执行时间，单位为us
            //PicZoom(&tConvertBuf.tPixelDatas, &tZoomBuf.tPixelDatas);
            //tCurrBuf = &tZoomBuf;
        }
        else  //否则直接复制到屏幕的显存上
        {
            iTopLeftX = (lcdWidth - tCurrBuf->tPixelDatas.iWidth) / 2;
            iTopLeftY = (lcdHeight - tCurrBuf->tPixelDatas.iHeight) / 2;
            
            err = PicMerge(iTopLeftX, iTopLeftY, &tCurrBuf->tPixelDatas, &tFrameBuf.tPixelDatas);   //合并到显示设备的framebuffer中
            if(err < 0)
            {
                printf("Pic Merge Failed!\n");
                return -1;
            }
        }
        #endif

        #if PTHREAD_FLAG
        sem_post(&sem0);
        #endif

        /* 将Framebuffer中的数据放回队列 */
        err = tVideoDevice.opr->PutFrame(&tVideoDevice, &tVideoBuf);
        if(err < 0)
        {
            printf("PutFrame for %s failed!\n",argv[1]);
            return -1;
        }

        //long long end = GetUs();
        //printf("TimeAll: %lld us\n", end - start);   //输出一帧图像的处理时间，单位为us

        frame_num++;
        //printf("Frame Num: %d\n", frame_num);
        #if PTHREAD_FLAG
        // 非阻塞等待线程结束
        if(pthread_tryjoin_np(tid1, NULL) == 0 && pthread_tryjoin_np(tid2, NULL) == 0)
        {
            printf("User input thread exit!\n");
            break;
        }
        #endif
    }

    ioctl(pxp_fd,PXP_IOC_PUT_CHAN,&pxp_handle.handle);  //释放PXP通道
    ioctl(pxp_fd,PXP_IOC_PUT_PHYMEM,&pxp_buffer);  //释放PXP物理内存
    close(pxp_fd);
    

    /* 关闭摄像头并释放掉前面申请的空间 */
    tVideoDevice.opr->StopDevice(&tVideoDevice);
    tVideoDevice.opr->ExitDevice(&tVideoDevice);

    ptVideoConvert->ConvertExit(&tConvertBuf);

    sem_destroy(&sem0);
    exit_touch_screen();
    close(tVideoDevice.iFd);
   

    return 0;
}